/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.rename; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.resources.IResource; import org.eclipse.text.edits.TextEdit; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusContext; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextEditChangeGroup; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.SourceRange; import org.eclipse.jdt.core.WorkingCopyOwner; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.search.FieldDeclarationMatch; import org.eclipse.jdt.core.search.MethodDeclarationMatch; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.internal.corext.SourceRangeFactory; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.corext.util.SearchUtils; import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; class RenameAnalyzeUtil { private static class ProblemNodeFinder { private ProblemNodeFinder() { //static } public static SimpleName[] getProblemNodes(ASTNode methodNode, VariableDeclaration variableNode, TextEdit[] edits, TextChange change) { String key= variableNode.resolveBinding().getKey(); NameNodeVisitor visitor= new NameNodeVisitor(edits, change, key); methodNode.accept(visitor); return visitor.getProblemNodes(); } private static class NameNodeVisitor extends ASTVisitor { private Collection<IRegion> fRanges; private Collection<SimpleName> fProblemNodes; private String fKey; public NameNodeVisitor(TextEdit[] edits, TextChange change, String key) { Assert.isNotNull(edits); Assert.isNotNull(key); fRanges= new HashSet<IRegion>(Arrays.asList(RefactoringAnalyzeUtil.getNewRanges(edits, change))); fProblemNodes= new ArrayList<SimpleName>(0); fKey= key; } public SimpleName[] getProblemNodes() { return fProblemNodes.toArray(new SimpleName[fProblemNodes.size()]); } //----- visit methods @Override public boolean visit(SimpleName node) { VariableDeclaration decl= getVariableDeclaration(node); if (decl == null) return super.visit(node); IVariableBinding binding= decl.resolveBinding(); if (binding == null) return super.visit(node); boolean keysEqual= fKey.equals(binding.getKey()); boolean rangeInSet= fRanges.contains(new Region(node.getStartPosition(), node.getLength())); if (keysEqual && !rangeInSet) fProblemNodes.add(node); if (!keysEqual && rangeInSet) fProblemNodes.add(node); /* * if (!keyEquals && !rangeInSet) * ok, different local variable. * * if (keyEquals && rangeInSet) * ok, renamed local variable & has been renamed. */ return super.visit(node); } } } static class LocalAnalyzePackage { public final TextEdit fDeclarationEdit; public final TextEdit[] fOccurenceEdits; public LocalAnalyzePackage(final TextEdit declarationEdit, final TextEdit[] occurenceEdits) { fDeclarationEdit = declarationEdit; fOccurenceEdits = occurenceEdits; } } private RenameAnalyzeUtil() { //no instance } static RefactoringStatus analyzeRenameChanges(TextChangeManager manager, SearchResultGroup[] oldOccurrences, SearchResultGroup[] newOccurrences) { RefactoringStatus result= new RefactoringStatus(); for (int i= 0; i < oldOccurrences.length; i++) { SearchResultGroup oldGroup= oldOccurrences[i]; SearchMatch[] oldSearchResults= oldGroup.getSearchResults(); ICompilationUnit cunit= oldGroup.getCompilationUnit(); if (cunit == null) continue; for (int j= 0; j < oldSearchResults.length; j++) { SearchMatch oldSearchResult= oldSearchResults[j]; if (! RenameAnalyzeUtil.existsInNewOccurrences(oldSearchResult, newOccurrences, manager)){ addShadowsError(cunit, oldSearchResult, result); } } } return result; } static ICompilationUnit findWorkingCopyForCu(ICompilationUnit[] newWorkingCopies, ICompilationUnit cu){ ICompilationUnit original= cu == null ? null : cu.getPrimary(); for (int i= 0; i < newWorkingCopies.length; i++) { if (newWorkingCopies[i].getPrimary().equals(original)) return newWorkingCopies[i]; } return null; } static ICompilationUnit[] createNewWorkingCopies(ICompilationUnit[] compilationUnitsToModify, TextChangeManager manager, WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException { pm.beginTask("", compilationUnitsToModify.length); //$NON-NLS-1$ ICompilationUnit[] newWorkingCopies= new ICompilationUnit[compilationUnitsToModify.length]; for (int i= 0; i < compilationUnitsToModify.length; i++) { ICompilationUnit cu= compilationUnitsToModify[i]; newWorkingCopies[i]= createNewWorkingCopy(cu, manager, owner, new SubProgressMonitor(pm, 1)); } pm.done(); return newWorkingCopies; } static ICompilationUnit createNewWorkingCopy(ICompilationUnit cu, TextChangeManager manager, WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException { ICompilationUnit newWc= cu.getWorkingCopy(owner, null); String previewContent= manager.get(cu).getPreviewContent(new NullProgressMonitor()); newWc.getBuffer().setContents(previewContent); newWc.reconcile(ICompilationUnit.NO_AST, false, owner, pm); return newWc; } private static boolean existsInNewOccurrences(SearchMatch searchResult, SearchResultGroup[] newOccurrences, TextChangeManager manager) { SearchResultGroup newGroup= findOccurrenceGroup(searchResult.getResource(), newOccurrences); if (newGroup == null) return false; IRegion oldEditRange= getCorrespondingEditChangeRange(searchResult, manager); if (oldEditRange == null) return false; SearchMatch[] newSearchResults= newGroup.getSearchResults(); int oldRangeOffset = oldEditRange.getOffset(); for (int i= 0; i < newSearchResults.length; i++) { if (newSearchResults[i].getOffset() == oldRangeOffset) return true; } return false; } private static IRegion getCorrespondingEditChangeRange(SearchMatch searchResult, TextChangeManager manager) { TextChange change= getTextChange(searchResult, manager); if (change == null) return null; IRegion oldMatchRange= createTextRange(searchResult); TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups(); for (int i= 0; i < editChanges.length; i++) { if (oldMatchRange.equals(editChanges[i].getRegion())) return TextEdit.getCoverage(change.getPreviewEdits(editChanges[i].getTextEdits())); } return null; } private static TextChange getTextChange(SearchMatch searchResult, TextChangeManager manager) { ICompilationUnit cu= SearchUtils.getCompilationUnit(searchResult); if (cu == null) return null; return manager.get(cu); } private static IRegion createTextRange(SearchMatch searchResult) { return new Region(searchResult.getOffset(), searchResult.getLength()); } private static SearchResultGroup findOccurrenceGroup(IResource resource, SearchResultGroup[] newOccurrences) { for (int i= 0; i < newOccurrences.length; i++) { if (newOccurrences[i].getResource().equals(resource)) return newOccurrences[i]; } return null; } //--- find missing changes in BOTH directions //TODO: Currently filters out declarations (MethodDeclarationMatch, FieldDeclarationMatch). //Long term solution: only pass reference search results in. static RefactoringStatus analyzeRenameChanges2(TextChangeManager manager, SearchResultGroup[] oldReferences, SearchResultGroup[] newReferences, String newElementName) { RefactoringStatus result= new RefactoringStatus(); HashMap<ICompilationUnit, SearchMatch[]> cuToNewResults= new HashMap<ICompilationUnit, SearchMatch[]>(newReferences.length); for (int i1= 0; i1 < newReferences.length; i1++) { ICompilationUnit cu= newReferences[i1].getCompilationUnit(); if (cu != null) cuToNewResults.put(cu.getPrimary(), newReferences[i1].getSearchResults()); } for (int i= 0; i < oldReferences.length; i++) { SearchResultGroup oldGroup= oldReferences[i]; SearchMatch[] oldMatches= oldGroup.getSearchResults(); ICompilationUnit cu= oldGroup.getCompilationUnit(); if (cu == null) continue; SearchMatch[] newSearchMatches= cuToNewResults.remove(cu); if (newSearchMatches == null) { for (int j = 0; j < oldMatches.length; j++) { SearchMatch oldMatch = oldMatches[j]; addShadowsError(cu, oldMatch, result); } } else { analyzeChanges(cu, manager.get(cu), oldMatches, newSearchMatches, newElementName, result); } } for (Iterator<Entry<ICompilationUnit, SearchMatch[]>> iter= cuToNewResults.entrySet().iterator(); iter.hasNext();) { Entry<ICompilationUnit, SearchMatch[]> entry= iter.next(); ICompilationUnit cu= entry.getKey(); SearchMatch[] newSearchMatches= entry.getValue(); for (int i= 0; i < newSearchMatches.length; i++) { SearchMatch newMatch= newSearchMatches[i]; addReferenceShadowedError(cu, newMatch, newElementName, result); } } return result; } private static void analyzeChanges(ICompilationUnit cu, TextChange change, SearchMatch[] oldMatches, SearchMatch[] newMatches, String newElementName, RefactoringStatus result) { Map<Integer, SearchMatch> updatedOldOffsets= getUpdatedChangeOffsets(change, oldMatches); for (int i= 0; i < newMatches.length; i++) { SearchMatch newMatch= newMatches[i]; Integer offsetInNew= new Integer(newMatch.getOffset()); SearchMatch oldMatch= updatedOldOffsets.remove(offsetInNew); if (oldMatch == null) { addReferenceShadowedError(cu, newMatch, newElementName, result); } } for (Iterator<SearchMatch> iter= updatedOldOffsets.values().iterator(); iter.hasNext();) { // remaining old matches are not found any more -> they have been shadowed SearchMatch oldMatch= iter.next(); addShadowsError(cu, oldMatch, result); } } /** * * @param change * @param oldMatches * @return Map <Integer updatedOffset, SearchMatch oldMatch> */ private static Map<Integer, SearchMatch> getUpdatedChangeOffsets(TextChange change, SearchMatch[] oldMatches) { Map<Integer, SearchMatch> updatedOffsets= new HashMap<Integer, SearchMatch>(); Map<Integer, Integer> oldToUpdatedOffsets= getEditChangeOffsetUpdates(change); for (int i= 0; i < oldMatches.length; i++) { SearchMatch oldMatch= oldMatches[i]; Integer updatedOffset= oldToUpdatedOffsets.get(new Integer(oldMatch.getOffset())); if (updatedOffset == null) updatedOffset= new Integer(-1); //match not updated updatedOffsets.put(updatedOffset, oldMatch); } return updatedOffsets; } /** * * @param change * @return Map <Integer oldOffset, Integer updatedOffset> */ private static Map<Integer, Integer> getEditChangeOffsetUpdates(TextChange change) { TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups(); Map<Integer, Integer> offsetUpdates= new HashMap<Integer, Integer>(editChanges.length); for (int i= 0; i < editChanges.length; i++) { TextEditChangeGroup editChange= editChanges[i]; IRegion oldRegion= editChange.getRegion(); if (oldRegion == null) continue; IRegion updatedRegion= TextEdit.getCoverage(change.getPreviewEdits(editChange.getTextEdits())); if (updatedRegion == null) continue; offsetUpdates.put(new Integer(oldRegion.getOffset()), new Integer(updatedRegion.getOffset())); } return offsetUpdates; } private static void addReferenceShadowedError(ICompilationUnit cu, SearchMatch newMatch, String newElementName, RefactoringStatus result) { //Found a new match with no corresponding old match. //-> The new match is a reference which was pointing to another element, //but that other element has been shadowed //TODO: should not have to filter declarations: if (newMatch instanceof MethodDeclarationMatch || newMatch instanceof FieldDeclarationMatch) return; ISourceRange range= getOldSourceRange(newMatch); RefactoringStatusContext context= JavaStatusContext.create(cu, range); String message= Messages.format( RefactoringCoreMessages.RenameAnalyzeUtil_reference_shadowed, new String[] {BasicElementLabels.getFileName(cu), BasicElementLabels.getJavaElementName(newElementName)}); result.addError(message, context); } private static ISourceRange getOldSourceRange(SearchMatch newMatch) { // cannot transfom offset in preview to offset in original -> just show enclosing method IJavaElement newMatchElement= (IJavaElement) newMatch.getElement(); IJavaElement primaryElement= newMatchElement.getPrimaryElement(); ISourceRange range= null; if (primaryElement.exists() && primaryElement instanceof ISourceReference) { try { range= ((ISourceReference) primaryElement).getSourceRange(); } catch (JavaModelException e) { // can live without source range } } return range; } private static void addShadowsError(ICompilationUnit cu, SearchMatch oldMatch, RefactoringStatus result) { // Old match not found in new matches -> reference has been shadowed //TODO: should not have to filter declarations: if (oldMatch instanceof MethodDeclarationMatch || oldMatch instanceof FieldDeclarationMatch) return; ISourceRange range= new SourceRange(oldMatch.getOffset(), oldMatch.getLength()); RefactoringStatusContext context= JavaStatusContext.create(cu, range); String message= Messages.format(RefactoringCoreMessages.RenameAnalyzeUtil_shadows, BasicElementLabels.getFileName(cu)); result.addError(message, context); } /** * This method analyzes a set of local variable renames inside one cu. It checks whether * any new compile errors have been introduced by the rename(s) and whether the correct * node(s) has/have been renamed. * * @param analyzePackages the LocalAnalyzePackages containing the information about the local renames * @param cuChange the TextChange containing all local variable changes to be applied. * @param oldCUNode the fully (incl. bindings) resolved AST node of the original compilation unit * @param recovery whether statements and bindings recovery should be performed when parsing the changed CU * @return a RefactoringStatus containing errors if compile errors or wrongly renamed nodes are found * @throws CoreException thrown if there was an error greating the preview content of the change */ public static RefactoringStatus analyzeLocalRenames(LocalAnalyzePackage[] analyzePackages, TextChange cuChange, CompilationUnit oldCUNode, boolean recovery) throws CoreException { RefactoringStatus result= new RefactoringStatus(); ICompilationUnit compilationUnit= (ICompilationUnit) oldCUNode.getJavaElement(); String newCuSource= cuChange.getPreviewContent(new NullProgressMonitor()); CompilationUnit newCUNode= new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(newCuSource, compilationUnit, true, recovery, null); result.merge(analyzeCompileErrors(newCuSource, newCUNode, oldCUNode)); if (result.hasError()) return result; for (int i= 0; i < analyzePackages.length; i++) { ASTNode enclosing= getEnclosingBlockOrMethod(analyzePackages[i].fDeclarationEdit, cuChange, newCUNode); // get new declaration IRegion newRegion= RefactoringAnalyzeUtil.getNewTextRange(analyzePackages[i].fDeclarationEdit, cuChange); ASTNode newDeclaration= NodeFinder.perform(newCUNode, newRegion.getOffset(), newRegion.getLength()); Assert.isTrue(newDeclaration instanceof Name); VariableDeclaration declaration= getVariableDeclaration((Name) newDeclaration); Assert.isNotNull(declaration); SimpleName[] problemNodes= ProblemNodeFinder.getProblemNodes(enclosing, declaration, analyzePackages[i].fOccurenceEdits, cuChange); result.merge(RefactoringAnalyzeUtil.reportProblemNodes(newCuSource, problemNodes)); } return result; } private static VariableDeclaration getVariableDeclaration(Name node) { IBinding binding= node.resolveBinding(); if (binding == null && node.getParent() instanceof VariableDeclaration) return (VariableDeclaration) node.getParent(); if (binding != null && binding.getKind() == IBinding.VARIABLE) { CompilationUnit cu= (CompilationUnit) ASTNodes.getParent(node, CompilationUnit.class); return ASTNodes.findVariableDeclaration( ((IVariableBinding) binding), cu); } return null; } private static ASTNode getEnclosingBlockOrMethod(TextEdit declarationEdit, TextChange change, CompilationUnit newCUNode) { ASTNode enclosing= RefactoringAnalyzeUtil.getBlock(declarationEdit, change, newCUNode); if (enclosing == null) enclosing= RefactoringAnalyzeUtil.getMethodDeclaration(declarationEdit, change, newCUNode); return enclosing; } private static RefactoringStatus analyzeCompileErrors(String newCuSource, CompilationUnit newCUNode, CompilationUnit oldCUNode) { RefactoringStatus result= new RefactoringStatus(); IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, oldCUNode); for (int i= 0; i < newProblems.length; i++) { IProblem problem= newProblems[i]; if (problem.isError()) result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, SourceRangeFactory.create(problem)))); } return result; } }